﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace KiDS_Projekat
{

	// Enums from Microsofts website (#defined in C++)
	public enum AddressFamily : int
	{
		Unknown = 0,
		InterNetworkv4 = 2,
		Ipx = 4,
		AppleTalk = 17,
		NetBios = 17,
		InterNetworkv6 = 23,
		Irda = 26,
		BlueTooth = 32
	}
	public enum SocketType : int
	{
		Unknown = 0,
		Stream = 1,
		DGram = 2,
		Raw = 3,
		Rdm = 4,
		SeqPacket = 5
	}
	public enum ProtocolType : int
	{
		BlueTooth = 3,
		Tcp = 6,
		Udp = 17,
		ReliableMulticast = 113
	}

	// Equivilent to C++s "SOCKET"
	public unsafe struct SOCKET
	{
		private void* handle;
		private SOCKET(int _handle)
		{
			handle = (void*)_handle;
		}
		public static bool operator ==(SOCKET s, int i)
		{
			return ((int)s.handle == i);
		}
		public static bool operator !=(SOCKET s, int i)
		{
			return ((int)s.handle != i);
		}
		public static implicit operator SOCKET(int i)
		{
			return new SOCKET(i);
		}
		public static implicit operator uint(SOCKET s)
		{
			return (uint)s.handle;
		}
		public override bool Equals(object obj)
		{
			return (obj is SOCKET) ? (((SOCKET)obj).handle == this.handle) : base.Equals(obj);
		}
		public override int GetHashCode()
		{
			return (int)handle;
		}
	}


	public struct timeval
	{
		public long tv_sec;
		public long tv_usec;
	}


	// fd_set used in 'select' method
	public unsafe struct fd_set
	{
		public const int FD_SETSIZE = 64;
		public uint fd_count;
		public fixed uint fd_array[FD_SETSIZE];
	}

	// C# equivilent to C++'s sockaddr_in / SOCKADDR_IN
	[StructLayout(LayoutKind.Sequential, Size = 16)]
	public struct sockaddr_in
	{
		public const int Size = 16;

		public short sin_family;
		public ushort sin_port;
		public struct in_addr
		{
			public uint S_addr;
			public struct _S_un_b
			{
				public byte s_b1, s_b2, s_b3, s_b4;
			}
			public _S_un_b S_un_b;
			public struct _S_un_w
			{
				public ushort s_w1, s_w2;
			}
			public _S_un_w S_un_w;
		}
		public in_addr sin_addr;
	}

	// WSAData structure, used in WSAStarutp
	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
	public unsafe struct WSAData
	{
		public ushort Version;
		public ushort HighVersion;
		[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
		public string Description;
		[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)]
		public string SystemStatus;
		public ushort MaxSockets;
		public ushort MaxUdpDg;
		sbyte* lpVendorInfo;
	}


	// Interface to ws2_32.dll
	public unsafe partial class Native
	{
		public const int AF_INET = 2;
		public const int SOCK_STREAM = 1;
		public const int PPROTO_TCP = 6;

		public const long FIONBIO = 0x8004667E;
		public const string INADDR_ANY = "0.0.0.0";

		public const int SOCKET_ERROR = -1;
		public const int INVALID_SOCKET = ~0;


		[DllImport("Ws2_32.dll")]
		public static extern int WSAStartup(ushort Version, out WSAData Data);

		[DllImport("Ws2_32.dll")]
		public static extern SocketError WSAGetLastError();

		[DllImport("Ws2_32.dll")]
		public static extern SOCKET socket(AddressFamily af, SocketType type,
											ProtocolType protocol);

		[DllImport("Ws2_32.dll")]
		public static extern int send(SOCKET s, byte* buf, int len, int flags);

		[DllImport("Ws2_32.dll")]
		public static extern int send(SOCKET s, char* buf, int len, int flags);

		[DllImport("Ws2_32.dll")]
		public static extern int recv(SOCKET s, char* buf, int len, int flags);

		[DllImport("Ws2_32.dll")]
		public static extern int recv(SOCKET s, byte* buf, int len, int flags);

		[DllImport("Ws2_32.dll")]
		public static extern SOCKET accept(SOCKET s, void* addr, int* addrsize);

		[DllImport("Ws2_32.dll")]
		public static extern int listen(SOCKET s, int backlog);

		[DllImport("Ws2_32.dll", CharSet = CharSet.Ansi)]
		public static extern uint inet_addr(string cp);

		[DllImport("Ws2_32.dll")]
		public static extern ushort htons(ushort hostshort);

		[DllImport("Ws2_32.dll")]
		public static extern int connect(SOCKET s, sockaddr_in* addr, int addrsize);

		[DllImport("Ws2_32.dll")]
		public static extern int ioctlsocket(SOCKET s, long cmd, ulong* argp);

		[DllImport("Ws2_32.dll")]
		public static extern int closesocket(SOCKET s);

		[DllImport("Ws2_32.dll")]
		public static extern int bind(SOCKET s, sockaddr_in* addr, int addrsize);

		[DllImport("Ws2_32.dll")]
		public static extern int select(int ndfs, fd_set* readfds, fd_set* writefds,
										 fd_set* exceptfds, timeval* timeout);

		[DllImport("Ws2_32.dll")]
		public static extern sbyte* inet_ntoa(sockaddr_in.in_addr _in);

		[DllImport("Ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		public static extern Int32 WSACleanup();

		/// <summary>
		/// Metod za primanje poruke
		/// </summary>
		/// <param name="socket">Socket kroz koji poruka treba da bude primljena</param>
		/// <returns>Primljen string. Vraca R ako nista nije stiglo</returns>
		public static unsafe string RecieveMessage(SOCKET socket)
		{
			char[] recvArr = new char[1000];

			int recvResult = -2; // -2 sigurno ne moze vratiti recv
			string message;
		
			fixed (char* recvBuf = recvArr)
			{
				recvResult = Native.recv(socket, recvBuf, 1000, 0);
				message = new string(recvBuf);
				if (recvResult == SOCKET_ERROR)
				{
					Console.WriteLine("Recive: " + Native.WSAGetLastError().ToString());
					Native.closesocket(socket);
					return "R";
				}
			}
			if (message.Length > 0)
				return message;
			else
				return "R";
		}

		/// <summary>
		/// Metod za slanje poruke u ASCII formatu
		/// </summary>
		/// <param name="message">Poruka koja treba da bude poslata</param>
		/// <param name="TIP_PORUKE">1, 2, 3 - pogledati dokumentaciju</param>
		/// <param name="socket">Socket kroz koji treba poslati poruku</param>
		/// <returns>Uspesnost slanja poruke</returns>
		public static unsafe bool SendASCIIMessage(string message, byte TIP_PORUKE, SOCKET socket)
		{
			byte[] msg = Encoding.ASCII.GetBytes("  " + message);
			msg[0] = TIP_PORUKE;
			msg[1] = (byte) message.Length;
						
			fixed (byte* pbuf = msg)
			{
				int sendResult = Native.send(socket, pbuf, msg.Length, 0);
				if (sendResult == SOCKET_ERROR)
				{
					Console.WriteLine("Sending: " + Native.WSAGetLastError().ToString());
					Native.closesocket(socket);
					return false;
				}
			}
			return true;
		}

		/// <summary>
		/// Metod za slanje poruke
		/// </summary>
		/// <param name="message">Poruka koja treba da bude poslata</param>
		/// <param name="socket">Socket kroz koji treba poslati poruku</param>
		/// <returns>Uspesnost slanja poruke</returns>
		public static unsafe bool SendMessage(string message, SOCKET socket)
		{
			fixed (char* pbuf = message)
			{
				int sendResult = Native.send(socket, pbuf, 2 * message.Length, 0);
				if (sendResult == SOCKET_ERROR)
				{
					Console.WriteLine("Sending: " + Native.WSAGetLastError().ToString());
					Native.closesocket(socket);
					return false;
				}
			}
			return true;
		}

		/*
		 *     int p1, p2, p3, p4;
	sscanf(bsParams.externalIPtxt, "%d.%d.%d.%d", &p1, &p2, &p3, &p4);
	bsParams.externalIP = (p1 & 0xFF) + ((p2 & 0xFF) << 8) + ((p3 & 0xFF) << 16) + ((p4 & 0xFF) << 24);
*/
		public static unsafe string Address(sockaddr_in sockaddr)
		{
			return sockaddr.sin_addr.S_un_b.s_b1 + "." + sockaddr.sin_addr.S_un_b.s_b2 + "." 
				+ sockaddr.sin_addr.S_un_b.s_b3 + "." + sockaddr.sin_addr.S_un_b.s_b4;
		}

		/// <summary>
		/// Metoda za prebacivanje adrese u bajtove
		/// </summary>
		/// <param name="str">Stringovna reprezentacija cvora u broj.broj.broj.broj obliku</param>
		/// <returns>brojcan prikaz adrese</returns>
		public static int AddressToBytes(string str)
		{
			string[] ip = str.Split('.');
			int p1 = Int32.Parse(ip[0]);
			int p2 = Int32.Parse(ip[1]);
			int p3 = Int32.Parse(ip[2]);
			int p4 = Int32.Parse(ip[3]);
			int bsParams;
			bsParams = (p1 & 0xFF) + ((p2 & 0xFF) << 8) + ((p3 & 0xFF) << 16) + ((p4 & 0xFF) << 24);
			return bsParams;

		}

		public static string IpToText(uint ipAddr)
		{
			return (ipAddr & 0xFF) + "." + ((ipAddr >> 8) & 0xFF) + "." + ((ipAddr >> 16) & 0xFF) + "." + ((ipAddr >> 24) & 0xFF);
		}
	}
}
